#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <stdbool.h>

#include "definition.h"

#define BASE64_MODE_LENGTH 4

/*
 * base64_valid_char, check if ch is a valid base64 character
 * return true if ch is valid, or return false
 */
static bool base64_valid_char(char ch)
{
    if(('a' <= ch && ch <= 'z') ||
       ('A' <= ch && ch <= 'Z') ||
       ('0' <= ch && ch <= '9') ||
       (ch == '+') ||
       (ch == '/') ||
       (ch == '='))
    {
        return true;
    } else {
        return false;
    }

    return false;
}

/*
 * base64_validate, check if encoded_msg is a valid base64 string
 * return NO_ERROR if encoded_msg is valid, or return error number
 */
static int base64_validate(const char* encoded_msg)
{
    size_t len;
    unsigned int i;

    len = strlen(encoded_msg);
    if((len % BASE64_MODE_LENGTH) != 0)
    {
        return BASE64_LENGTH_ERROR;
    }

    for(i = 0; i < len; ++i)
    {
        if(!base64_valid_char(encoded_msg[i]))
        {
            return BASE64_CHAR_ERROR;
        }
    }

    for(i = 0; i < len; ++i)
    {
        char ch = encoded_msg[i];
        if(ch == '=')
        {
            if(len - i > 2)
            {
                return BASE64_ENDING_ERROR;
            }
        }

        if((i == len - 1) && (encoded_msg[i] != '='))
        {
            if(encoded_msg[i - 1] == '=')
            {
                return BASE64_ENDING_ERROR;
            }
        }
    }

    return NO_ERROR;
}

/*
 * base64_encode, encode data into base64 string
 * return NO_ERROR if encode succeeds, or return error number
 */
int base64_encode(unsigned char* in, unsigned int in_size, unsigned char* out, unsigned int *out_size)
{
    BIO *b64, *bio;
    BUF_MEM *bptr = NULL;

    if(!in || !out || !out_size || (in_size == 0))
        return BASE64_ENCODE_ERROR;

    b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    bio = BIO_new(BIO_s_mem());
    bio = BIO_push(b64, bio);

    BIO_write(bio, in, in_size);
    BIO_flush(bio);

    BIO_get_mem_ptr(bio, &bptr);
    memcpy(out, bptr->data, bptr->length);
    *out_size = bptr->length;

    BIO_free_all(bio);

    return NO_ERROR;
}

/*
 * base64_decode, decode base64 string into data
 * return NO_ERROR if decode succeeds, or return error number
 */
int base64_decode(unsigned char* in, unsigned int in_size, unsigned char* out, unsigned int *out_size)
{
    BIO *b64, *bio;
    int ret = 0;
    
    if(!in || !out || !out_size || (in_size == 0))
        return BASE64_DECODE_ERROR;

    ret = base64_validate((char*)in);
    if(ret != NO_ERROR)
        return ret;



    b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);

    bio = BIO_new_mem_buf(in, in_size);
    bio = BIO_push(b64, bio);

    *out_size = BIO_read(bio, out, in_size);

    BIO_free_all(bio);

    return NO_ERROR;
}
